/*
 * @Author: 卢金辉 3400170271@qq.com
 * @Date: 2025-06-27 10:18:59
 * @LastEditors: 卢金辉 3400170271@qq.com
 * @LastEditTime: 2025-06-29 11:05:45
 * @FilePath: \见习2-卢金辉\Calculator.java
 * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
 */

import javax.swing.Timer;
import javax.sound.sampled.*;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JButton;
import javax.swing.JTextField;
import javax.script.ScriptEngine;
import javax.swing.SwingUtilities;
import javax.script.ScriptEngineManager;

import java.awt.Font;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

// ActionListener 是java中事件处理机制的核心部分
//为了建立计算器组件和用户操作之间的相应
//继承JFrame 为了直接复用swing框架中窗口管理的能力
//为了子类可以直接使用JFrame public 的方法
public class Calculator extends JFrame implements ActionListener
{
    //声明界面组件
    //上下俩个容器面板 文本框区域 按钮区域
    private JPanel north, center;
    //文本框 显示计算过程，结果或日期
    private JTextField field;
    //按钮
    private JButton[] btns;

    //初始化窗口 -构造器 构造方法 无参构造器
    public Calculator() 
    {
        // 设置窗口标题
        setTitle("科学计算器");
        // 设置窗口大小
        setSize(700, 800);
        // 设置关闭操作
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        //设置窗口居中显示
        setLocationRelativeTo(null);
        // 初始化界面组件
        initComponents();
        //设置窗口显示
        setVisible(true);
    }

    // 初始化界面组件
    private void initComponents() 
    {
        // 创建面板容器
        // 容器面板 文本框区域
        north = new JPanel();
        // 容器面板 按钮区域
        center = new JPanel();
        //文本框 显示计算过程，结果或日期
        field = new JTextField();
        // 设置输入框大小 首选尺寸
        field.setPreferredSize(new Dimension(700, 150));
        // 水平对齐方式为右对齐
        field.setHorizontalAlignment(JTextField.RIGHT);
        // 设置输入框背景颜色
        field.setBackground(java.awt.Color.WHITE);
        // 设置输入框不可编辑
        field.setEditable(false);
        // 设置输入框字体
        // 字体 SansSerif 字体样式 BOLD 粗体 字号
        field.setFont(new Font("SansSerif", Font.BOLD, 40));
        //设置字体颜色
        field.setForeground(java.awt.Color.BLACK);
        String[] str =new String[]{
            "AC", "C", "T", "S", "÷",
            "7", "8", "9", "√", "×",
            "4", "5", "6", "n!", "-",
            "1", "2", "3", "Π", "+",
            ".", "0", "(", ")", "="
        };
        //按钮数组内容
        btns = new JButton[str.length];
        for (int i = 0; i < btns.length; i++) 
        {
            // 创建按钮
            btns[i] = new JButton(str[i]);
            // 设置按钮大小
            btns[i].setFont(new Font("SansSerif", Font.BOLD, 30));
            // 设置按钮分区颜色
            btns[i].setFocusPainted(false);//让按钮在获得焦点时，不会显示聚焦边框，使界面更加整洁。
            btns[i].setBackground(new java.awt.Color(245, 245, 245)); //把按钮的背景色设为浅灰色（RGB 值为 245, 245, 245）。
            btns[i].setForeground(java.awt.Color.DARK_GRAY);//将按钮上文字的颜色设为深灰色
            btns[i].setBorder(javax.swing.BorderFactory.createLineBorder(new java.awt.Color(200,200,200), 2, true));
            //给按钮添加一个 2 像素宽的圆角边框，边框颜色是浅灰色（RGB 值为 200, 200, 200）。
            // 运算符按钮高亮
            if ("+-×÷=".contains(str[i])) 
            {
                btns[i].setBackground(new java.awt.Color(220, 230, 250));
                btns[i].setForeground(new java.awt.Color(40, 80, 200));
            }
            // 功能键高亮
            if ("AC".equals(str[i]) || "C".equals(str[i])) 
            {
                btns[i].setBackground(new java.awt.Color(255, 220, 220));
                btns[i].setForeground(java.awt.Color.RED);
            }
        }
        //给第四个按钮设置图标
        btns[3].setIcon(new ImageIcon(getClass().getResource("/图片.jpg")));//这是 Java 中用来创建图标的类
        // 初始化按钮
        // north 顶部布局 使用BorderLayout 边界布局
        north.setLayout(new BorderLayout()); 
        // center 中间布局 使用5行5列布局
        center.setLayout(new GridLayout(5, 5));
        //组件设置
        //field 文本框 添加到 north 顶部面板 使用BorderLayout 居中
        north.add(field, BorderLayout.CENTER);
        // 将按钮添加到 center 中间面板 使用 GridLayout 布局
        for (JButton btn : btns) 
        {
            center.add(btn);
            // 为按钮添加事件监听器
            // this代表当前类 Calculator 实例
            btn.addActionListener(this);
        }
        //将 north 顶部面板添加到窗口的顶部
        add(north, BorderLayout.NORTH);
        //将 center 中间面板添加到窗口的中间
        add(center, BorderLayout.CENTER);
    }

    private Timer timer = null; // 定义一个定时器变量

    //处理按键文本
    private void setBtnText(String text)
    {
        switch (text) 
        {
            case "AC":

                //按下 AC 按钮时，关闭显示实时时间的定时器
                // 这里假设有一个定时器变量 timer 用于更新时间
                if (timer != null)
                {
                    timer.stop();
                    timer = null;
                }
                field.setText("");
                break;
            case "C":
            String numText = field.getText();
            if (numText.length() != 0) 
            {
                // 输入框的内容不为空
                // System.out.println("输入框的内容不为空:" + numText);
                String lastText = numText.substring(0, numText.length() - 1);
                // System.out.println("lastText:" + lastText);
                // 将截取后的文本设置到输入框
                field.setText(lastText);
            }
                break;
            case "S":
                playSound("/click2.wav");
                int lucky = (int)(Math.random() * 100) + 1;
                field.setText("你的今日幸运数字是：" + lucky);
                break;
            case "T":
                field.setText(java.time.LocalDate.now().toString() + " " + java.time.LocalTime.now().toString());
                //会获取当前系统的日期和时间
                //让时间实时更新
                if (timer != null) 
                {
                    timer.stop(); // 停止之前的定时器
                }
                timer = new Timer(1000, e -> field.setText(java.time.LocalDate.now().toString() + " " + java.time.LocalTime.now().toString()));
                timer.start();
                break;
            case "=":
                playSound("/click.wav");
                Calculate();
                break;   
            case ".":
            case "Π":
            case "(":
            case ")":
            case "+":
            case "-":
            case "×":
            case "÷":
            case "0": case "1": case "2": case "3": case "4":
            case "5": case "6": case "7": case "8": case "9":
                String current = field.getText();
                if (isValidInput(current, text)) 
                {
                    // 如果是小数点且当前为空，自动补0
                    if (".".equals(text) && (current == null || current.isEmpty())) 
                    {
                        field.setText("0.");
                    } else if ("Π".equals(text)) 
                    {
                        field.setText(current + Math.PI);
                    } else 
                    {
                        field.setText(current + text);
                    }
                }
                break;
            case "√":
                if (isNumeric(field.getText())) 
                {
                    double result = Math.sqrt(Double.parseDouble(field.getText()));
                    field.setText(String.valueOf(result));
                }
                break;
            case "n!":
                if (isNumeric(field.getText())) 
                {
                    int num = Integer.parseInt(field.getText());//会把获取到的字符串转换为int类型的整数。
                    field.setText(String.valueOf(factorial(num)));
                }
                break;
            default:
                // 其它输入不处理
                break;
            }
    }

    // 在类中添加一个判断字符串是否为数字的方法
    private boolean isNumeric(String str) 
    {
        if (str == null || str.isEmpty()) 
        {
            // 空字符串不是数字
            return false; 
        }
        try 
        {
            // 尝试将字符串转换为数字
            Double.parseDouble(str); 
            return true; // 如果转换成功，则是数字
        } 
        catch (NumberFormatException e) 
        {
            return false; // 如果抛出异常，则不是数字
        }
        
    }

    //执行计算功能
    private void Calculate() 
    {
        // 获取输入框中的表达式
        String expression = field.getText();
        //使用js引擎
        //初始化js引擎  使用ScriptEngine解析数学表达式
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("JavaScript");
        //try-catch 处理异常
        try
        {
            // 计算表达处理乘式的结果和除式的结果
            expression = expression.replace("×", "*").replace("÷", "/");
            Object result = engine.eval(expression);
            // 将结果转换为字符串并显示在输入框中
            field.setText(result.toString());
        } 
        //catch中放的是处理异常的代码
        catch (Exception e) 
        {
            // 如果计算出错，清空输入框并显示错误信息
            field.setText("计算错误");
        }

    }

    // 计算阶乘的方法
    private long factorial(int n) 
    {
        if (n < 0) 
        {
            throw new IllegalArgumentException("负数没有阶乘");
        }
        long result = 1;
        for (int i = 2; i <= n; i++) 
        {
            result *= i;
        }
        return result;
    }

    // 校验输入是否合法
    private boolean isValidInput(String currentText, String input) 
    {
        // 操作符和小数点
        String ops = "+×÷";
        // 1. 操作符不能出现在开头，如果输入是操作符（不包括负号）且当前表达式为空，返回false
        if ((ops.contains(input)) && (currentText.isEmpty() || currentText.endsWith("+") || currentText.endsWith("-") || currentText.endsWith("×") || currentText.endsWith("÷") || currentText.endsWith("."))) 
        {
            return false;
        }
        // 2. 小数点不能连续出现，且同一数字中只能有一个小数点
        if (input.equals(".")) 
        {
            // 先防止连续小数点
            if (currentText.endsWith(".")) 
            {
                return false;
            }
            // 再防止同一数字多次小数点
            // 从后往前找到最近的运算符
            int i = currentText.length() - 1;
            while (i >= 0 && "0123456789.".indexOf(currentText.charAt(i)) != -1) 
            {
                if (currentText.charAt(i) == '.') 
                {
                    return false; // 已有小数点
                }
                i--;
            }
        }
        // 3. 操作符不能连续出现（但允许 - 作为负号出现在开头或其他运算符后）
        if ("+×÷".contains(input) && !currentText.isEmpty() && "+-×÷".contains("" + currentText.charAt(currentText.length() - 1))) 
        {
            return false;
        }
        // 允许 - 作为负号
        if (input.equals("-")) 
        {
            // 只要不是连续两个 -，都允许
            if (currentText.endsWith("-")) 
            {
                return false;
            }
        }
        // 4.阻止连续输入Π
        if (input.equals("Π") && currentText.endsWith(String.valueOf(Math.PI))) 
        {
            return false;
        }
        // 如果没有被前面的条件拦截，则输入有效
        return true;
    }

    // 实现 ActionListener 接口中的抽象方法 处理事件
    @Override
    public void actionPerformed(ActionEvent a) 
    {
        // 此处可以捕获到 ActionListener 监听的事件
        // System.out.println(a.getSource());
        // 获取被点击的按钮 或者按钮的文字
        Object source = a.getSource();
        if (source instanceof JButton) 
        {
        JButton btn = (JButton) source;
        String text = btn.getText();
        // System.out.println(text);
        // 处理对应按键文字
        setBtnText(text);
        }
    }

    // 播放音效的方法，可放在类末尾
    private void playSound(String soundFile) 
    {
        try 
        {
            AudioInputStream audioIn = AudioSystem.getAudioInputStream(getClass().getResource(soundFile));
            Clip clip = AudioSystem.getClip();
            clip.open(audioIn);
            clip.start();
        } catch (Exception e) 
        {
            // 可以忽略或打印错误
        }
        //System.out.println(getClass().getResource("/click.wav"));
    }

    public static void main(String[] args) 
    {
        // System.out.println("hello,Calculator");
        // 创建类的对象 - 线程不安全
        // new Calculator();
        // 是 Java Swing 开发中确保线程安全的核心代码
        // EDT 线程保障
        // Swing 要求所有 GUI 操作（组件创建、属性修改、事件处理）必须在 Event Dispatch Thread (EDT) 执行。这里代码通过
        // invokeLater 将 Calculator 类的实例化操作（即 GUI 初始化）提交到 EDT 队列，确保线程安全。
        // 异步执行机制
        // invokeLater 会将传入的 Runnable 任务（此处为 Calculator::new 方法引用）放入 EDT 的任务队列，由 EDT
        // 按顺序执行。这避免了直接操作 GUI 导致的线程竞争问题。
        SwingUtilities.invokeLater(Calculator::new);    
    }
}